简介
Google Protocol Buffer简称Protobuf,是由Google开发的用来序列化、反序列化数据结构的技术,它支持C++、Java、python等多种语言。
- 
安装下载源码:点击这里
 安装:1 
 2
 3
 4./configure --prefix=$INSTALL_DIR 
 make
 make check
 make install
一个例子
使用Protobuf通常需要:
1、定义消息格式。
2、生成.h和.cc文件
3、使用Protobuf提供的API写代码
定义消息格式
消息格式定义文件一般以.proto后缀结尾,可以在.proto文件中添加想要序列化的消息。一个消息由类型和消息名组成。下面是addressbook.proto消息定义:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package tutorial;
message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }
  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }
  repeated PhoneNumber phone = 4;
}
message AddressBook {
  repeated Person person = 1;
}
上面一package开头,作用了命名空间类似,可以防止命名冲突。
消息定义有类型和消息名组成,类型包括bool, int32, float, double, string。消息名后面的括号[]中default=表示这个消息的默认值。
消息后面会后=1,=2这样的字段,这是用来标记消息在二进制编码中的唯一性的。编号1-15编码占用空间小于1字节,为了优化考虑,经常用到的字段建议使用编号1-15。
每个消息类型前面还有个修饰符:required:表示必须提供初始值,否则这个消息字段是未初始化的。optional:表示如果没有提供初始值,那么可以指定默认值。repeated:表示该消息字段可能会重复,出现多次或者0次。
生成.h和.cc文件
使用命令1
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto
生成addressbook.pb.cc addressbook.pb.h文件
对应API
可以在addressbook.pb.h中拿出一部分代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38// required string name = 1;
  inline bool has_name() const;
  inline void clear_name();
  static const int kNameFieldNumber = 1;
  inline const ::std::string& name() const;
  inline void set_name(const ::std::string& value);
  inline void set_name(const char* value);
  inline void set_name(const char* value, size_t size);
  inline ::std::string* mutable_name();
  inline ::std::string* release_name();
  inline void set_allocated_name(::std::string* name);
  // required int32 id = 2;
  inline bool has_id() const;
  inline void clear_id();
  static const int kIdFieldNumber = 2;
  inline ::google::protobuf::int32 id() const;
  inline void set_id(::google::protobuf::int32 value);
  // optional string email = 3;
  inline bool has_email() const;
  inline void clear_email();
  static const int kEmailFieldNumber = 3;
  inline const ::std::string& email() const;
  inline void set_email(const ::std::string& value);
  inline void set_email(const char* value);
  inline void set_email(const char* value, size_t size);
  inline ::std::string* mutable_email();
  inline ::std::string* release_email();
  inline void set_allocated_email(::std::string* email);
  // repeated .tutorial.Person.PhoneNumber phone = 4;
  inline int phone_size() const;
  inline void clear_phone();
  static const int kPhoneFieldNumber = 4;
  inline const ::tutorial::Person_PhoneNumber& phone(int index) const;
  inline ::tutorial::Person_PhoneNumber* mutable_phone(int index);
  inline ::tutorial::Person_PhoneNumber* add_phone();
可以看出,对于required和optional字段,都有set_ 和has_方法,前者用来设置字段值,后者用来检验是否设置了值。所有字段都有clear_方法,用来清除设置的值。
对于repeated字段,有_size()方法,用来检查有多少个这样的字段。
可以通过字段名()的方法来获取字段的值,但要注意返回的是常引用。要想修改字段的是,通过mutable_字段名()来获取指向字段的指针。
###序列化和解析
每个protobuf类都有API来序列化消息为二进制或解析二进制消息:bool SerializeToString(string* output) const;: 把消息序列化为二进制的stringbool ParseFromString(const string& data);: 解析二进制的string消息bool SerializeToOstream(ostream* output) const;: 把消息序列化为二进制输出流。bool ParseFromIstream(istream* input);: 从二进制输入流解析消息。
实战
二进制文件的读写
写消息,把消息序列化,写到二进制文件中,保存到硬盘。
writer.cpp把消息序列化写到硬盘,reader.cpp从硬盘读取消息并打印。注意编译时要链接protobuf库,使用选线-lprotobuf。
writer.cpp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using std::string;
int main()
{
	tutorial::AddressBook address_book;
	//添加一个用户信息
	tutorial::Person* person = address_book.add_person();
	person->set_name("xiaoming");
	person->set_id(1);
	
	tutorial::Person::PhoneNumber* phone_number = person->add_phone();
  phone_number->set_number("13812345678");
  phone_number->set_type(tutorial::Person::MOBILE);
  	
  //再添加一个用户
  person = address_book.add_person();
	person->set_name("xiaosan");
	person->set_id(2);
	person->set_email("xiaosan@email.com");
	phone_number = person->add_phone();
  phone_number->set_number("02012345678");
  phone_number->set_type(tutorial::Person::HOME);
  	
  //保存为文本
  {
  	std::fstream output("./addressbook.bin", std::ios::out | std::ios::binary | std::ios::trunc); 
    if (!address_book.SerializeToOstream(&output)) { 
      std::cerr << "Failed to parse address book." << std::endl; 
      return -1; 
    } 
  }
  return 0;
}
reader.cpp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace  std;
	
int main()
{
	tutorial::AddressBook address_book;
	{
		fstream input("./addressbook.bin", ios::in | ios::binary);
		if(!address_book.ParseFromIstream(&input))
		{
			cerr<<"Failed to parse address book."<<endl;
			return -1;
		}
	}
	
	//输出address_book信息
	for(int i = 0; i< address_book.person_size(); ++i)
	{
		const tutorial::Person& person = address_book.person(i);
		cout<<"Person ID: "<<person.id()<<endl;
		cout<<" Name: "<<person.name()<<endl;
		if( person.has_email())
		{
			cout<<" E-mail address: "<<person.email()<<endl;
		}
		for( int j = 0; j < person.phone_size(); ++j)
		{
			const tutorial::Person::PhoneNumber& phone_number = person.phone(j);
			
			switch(phone_number.type())
			{
				case tutorial::Person::MOBILE:
					cout<<" Mobile phone #";
					break;
				case tutorial::Person::HOME:
					cout<<" Home phone #";
					break;
				case tutorial::Person::WORK:
					cout<<" Work phone #";
					break;
			}
			cout<< phone_number.number()<<endl;
		}
	}
}
文本文件的读写
protobuf可以以文本形式保存到文本中,也可以从文中读取消息。配置文件可以以protobuf文本形式保存使用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53#include <iostream>
#include <fstream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/text_format.h>
#include "addressbook.pb.h"
using namespace  std;
using google::protobuf::io::FileInputStream;
using google::protobuf::io::FileOutputStream;
using google::protobuf::Message;
//从文本读到protobuf中
bool ReadProtoFromTextFile(const char* filename, Message* proto) {
  int fd = open(filename, O_RDONLY);
  FileInputStream* input = new FileInputStream(fd);
  bool success = google::protobuf::TextFormat::Parse(input, proto);
  delete input;
  close(fd);
  return success;
}
//写到protobuf中
void WriteProtoToTextFile(const Message& proto, const char* filename) {
  int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
  FileOutputStream* output = new FileOutputStream(fd);
  google::protobuf::TextFormat::Print(proto, output);
  delete output;
  close(fd);
}
	
int main()
{
	tutorial::AddressBook address_book;
	{
		fstream input("./addressbook.bin", ios::in | ios::binary);
		if(!address_book.ParseFromIstream(&input))
		{
			cerr<<"Failed to parse address book."<<endl;
			return -1;
		}
	}
	
	//写到文本中
	WriteProtoToTextFile(address_book, "./addressbook.txt");
	return 0;
	
}